use cmsis_rtos2_rs::{
    osWaitForever,
    static_mem::{mq_size, EventFlags, MessageQueue},
};
use core::ptr::null;
use periph_utils::stm32f1;
use periph_utils::{stm32f1::stm32f103::interrupt, DmaConfig, GpioConfig, UartInit};
use stm32f1::stm32f103::{Interrupt, NVIC};
use utils::{ByteBuffer, MutCell, VolatileHandle};

use crate::app::proto::Proto;

#[derive(GpioConfig, DmaConfig, UartInit)]
#[gpio(
    init = "gpio_init",
    io(name = "tx", io = "PA9", mode = "AF_PP"),
    io(name = "rx", io = "PA10", mode = "IN_FLOAT")
)]
#[dma(
    init = "dma_init",
    ch(name = "usart1_tx", dma = 1, ch = 4, size = 8, dir = "write", tcie),
    ch(name = "usart1_rx", dma = 1, ch = 5, size = 8, dir = "read", circ)
)]
#[uart_config(uart = 1, size = 8, baud_rate = 115200)]
struct Pin;

unsafe impl Send for ByteView {}
unsafe impl Sync for ByteView {}

#[derive(Clone, Copy)]
pub struct ByteView {
    pub ptr: *const u8,
    pub len: usize,
}

impl ByteView {
    pub const fn const_new() -> Self {
        Self {
            ptr: null(),
            len: 0,
        }
    }
}

const FLAG_TC: u32 = 0x01;
const MSG_COUNT: usize = 5;

struct Context {
    write_buf: ByteBuffer<255>,
    recv_buf: ByteBuffer<255>,
    recv_index: MutCell<usize>,
    mq: MessageQueue<ByteView, MSG_COUNT, { mq_size::<ByteView>(MSG_COUNT) }>,
    event_flags: EventFlags,
}

static CTX: Context = Context::uninit();

impl Context {
    const fn uninit() -> Self {
        Self {
            write_buf: ByteBuffer::const_new(),
            recv_buf: ByteBuffer::const_new(),
            recv_index: MutCell::const_new(0),
            mq: MessageQueue::uninit(),
            event_flags: EventFlags::uninit(),
        }
    }

    unsafe fn init_once(&self) {
        self.mq.init_once("cmd_uart");
        self.event_flags.init_once("cmd_uart");
        self.event_flags.set_flags(FLAG_TC);
    }
}

pub unsafe fn init() {
    CTX.init_once();
    
    Pin::gpio_init();
    Pin::dma_init();
    Pin::usart1_init();

    Pin::usart1_rx_dma1_ch5_read(
        CTX.recv_buf.as_ptr() as u32,
        USART1.dr.as_ptr() as u32,
        CTX.recv_buf.size() as u16,
    );

    NVIC::unmask(Interrupt::USART1);
    NVIC::unmask(Interrupt::DMA1_CHANNEL4);
}

pub fn take() -> ByteView {
    CTX.mq.take()
}

pub fn poll(timeout: u32) -> Option<ByteView> {
    CTX.mq.poll(timeout).ok()
}

pub fn write<'a, T: Proto<'a> + ?Sized>(x: &T) {
    _ = CTX.event_flags.wait_any_flags(FLAG_TC, osWaitForever);
    let all_buf = unsafe { CTX.write_buf.as_mut_slice() };
    let write_buf = x.serialize(all_buf);
    Pin::usart1_tx_dma1_ch4_write(
        write_buf.as_ptr() as u32,
        USART1.dr.as_ptr() as u32,
        write_buf.len() as u16,
    );
}

unsafe fn recv_idle() {
    let index = CTX.recv_buf.size() - Pin::usart1_rx_dma1_ch5_ndtr() as usize;
    let last_index = CTX.recv_index.volatile_read();

    if index == last_index {
        return;
    }

    if index > last_index {
        let bw = ByteView {
            ptr: CTX.recv_buf.as_ptr().add(last_index),
            len: index - last_index,
        };
        _ = CTX.mq.post(&bw);
    } else {
        let bw = ByteView {
            ptr: CTX.recv_buf.as_ptr().add(last_index),
            len: CTX.recv_buf.size() - last_index,
        };
        _ = CTX.mq.post(&bw);
        if index != 0 {
            let bw = ByteView {
                ptr: CTX.recv_buf.as_ptr(),
                len: index,
            };
            _ = CTX.mq.post(&bw);
        }
    }
    CTX.recv_index.as_mut().volatile_write(index);
}

#[interrupt]
unsafe fn USART1() {
    if Pin::usart1_is_idle() {
        Pin::usart1_clear_idle();
        recv_idle();
    }
}

#[interrupt]
unsafe fn DMA1_CHANNEL4() {
    if Pin::usart1_tx_dma1_ch4_is_tc() {
        Pin::usart1_tx_dma1_ch4_clear_flags();
        _ = CTX.event_flags.set_flags(FLAG_TC);
    }
}
